<?php

namespace Xnrcms\BaseTools\Rsa;

class Rsa
{
    private array $config   = [];
    private array $signData = [];

    public function __construct(array $config = [])
    {
        $this->signData     = [];
        $this->config       = array_merge([
            "digest_alg"        => "sha512",
            "private_key_bits"  => 2048,
            "private_key_type"  => OPENSSL_KEYTYPE_RSA
        ],$config);
    }

    /**
     * 获取当前配置
     * @return array
     */
    public function getConfig(): array
    {
        return $this->config;
    }

    /**
     * 创建公钥和私钥
     * @param string $filepath
     * @param string $filename
     * @return array
     */
    public function createRsa(string $filepath = '',string $filename = ''): array
    {
        $tt             = date('YmdHis');
        $filepath       = !empty($filepath) ? $filepath : '';
        $filename       = (!empty($filename) ? $filename : $tt);
        $config         = [
            "digest_alg"        => $this->config['digest_alg'],
            "private_key_bits"  => $this->config['private_key_bits'],
            "private_key_type"  => $this->config['private_key_type'],
        ];

        //生成私钥
        $rsa            = openssl_pkey_new($config);
        openssl_pkey_export($rsa, $priKey, null, $config);

        // 生成公钥
        $rsaPri         = openssl_pkey_get_details($rsa);
        $pubKey         = $rsaPri['key'];
        $createRes      = ['success'=>'ok'];

        //设置了目录就生成相应文件
        if (!empty($filepath)){
            try {
                file_put_contents(DIRECTORY_SEPARATOR . trim($filepath,'/') . DIRECTORY_SEPARATOR . $filename . '_public' . '.key', $pubKey);
                file_put_contents(DIRECTORY_SEPARATOR . trim($filepath,'/') . DIRECTORY_SEPARATOR . $filename . '_private' . '.key', $priKey);
                return $createRes;
            }catch (\Exception $exception){
                return ['success'=> 'no','error'=>$exception->getMessage()];
            }
        }

        unset($config,$rsa,$rsaPri);

        return array_merge($createRes,['priKey'=>$priKey,'pubKey'=>$pubKey]);
    }

    /**
     * 用私钥加密
     * @param array $param
     * @param string $priKey
     * @return string|null
     */
    public function priEncrypt(array $param, string $priKey): ?string
    {
        if (empty($param) || empty($priKey)) {
            return null;
        }

        $param          = http_build_query($param);
        $pem            = str_contains($priKey,'BEGIN PRIVATE KEY') ? $priKey : "-----BEGIN PRIVATE KEY-----\n" . $this->formatKey($priKey) . "\n-----END PRIVATE KEY-----";
        $priKey         = openssl_pkey_get_private($pem);
        $result         = openssl_private_encrypt(json_encode($param), $encrypted, $priKey);

        //设置调试数据
        $this->setDebugData($param,$encrypted,$priKey);

        if ($result) {
            return base64_encode($encrypted);
        }

        return null;
    }

    /**
     * 私钥解密
     * @param string $encryption
     * @param string $priKey
     * @return string|null
     */
    public function priDecrypt(string $encryption, string $priKey): ?string
    {
        if (empty($encryption) || empty($priKey)) {
            return null;
        }

        $pem            = str_contains($priKey,'BEGIN PRIVATE KEY') ? $priKey : "-----BEGIN PRIVATE KEY-----\n" .$priKey."\n-----END PRIVATE KEY-----";
        $priKey         = openssl_pkey_get_private($pem);
        $encrypted      = base64_decode($encryption);
        $result         = openssl_private_decrypt($encrypted, $decrypted, $priKey);

        if ($result) {
            return $decrypted;
        }

        return null;
    }

    /**
     * 公钥加密
     * @param array $param
     * @param string $pubKey
     * @return string|null
     */
    public function pubEncrypt(array $param, string $pubKey): ?string
    {
        if (empty($param) || empty($pubKey)) {
            return null;
        }

        $param          = http_build_query($param);
        $pem            = str_contains($pubKey,'BEGIN PUBLIC KEY') ? $pubKey : "-----BEGIN PUBLIC KEY-----\n" . $pubKey ."\n-----END PUBLIC KEY-----";
        $pubKey         = openssl_pkey_get_public($pem);
        $result         = openssl_public_encrypt($param, $encrypted, $pubKey);

        //设置调试数据
        $this->setDebugData($param,$encrypted,$pubKey);

        if ($result) {
            return base64_encode($encrypted);
        }
        return null;
    }

    /**
     * 公钥解密
     * @param string $encryption
     * @param string $pubKey
     * @return string|null
     */
    public function pubDecrypt(string $encryption, string $pubKey): ?string
    {
        if (empty($encryption) || empty($pubKey)) {
            return null;
        }

        $pem            = str_contains($pubKey,'BEGIN PUBLIC KEY') ? $pubKey : "-----BEGIN PUBLIC KEY-----\n" .$pubKey."\n-----END PUBLIC KEY-----";
        $pubKey         = openssl_pkey_get_public($pem);
        $encryption     = base64_decode($encryption);
        $result         = openssl_public_decrypt($encryption, $decrypted, $pubKey);
        if ($result) {
            return $decrypted;
        }
        return null;
    }

    /**
     * 使用公钥对接口提交的数据进行验签
     * @param array $param
     * @param string $signature
     * @param string $pubKey
     * @return bool
     */
    public function verifyRsaSign(array $param, string $signature, string $pubKey): bool
    {
        $pem        = str_contains($pubKey,'BEGIN PUBLIC KEY') ? $pubKey : "-----BEGIN PUBLIC KEY-----\n" .$pubKey."\n-----END PUBLIC KEY-----";
        $pubKeyId   = openssl_get_publickey($pem); // 获取资源标识

        unset($param['sign']);

        $param      = array_filter($param);
        ksort($param);

        $response   = stripslashes(http_build_query($param)); // 删除数据中的反斜杠
        $signature  = base64_decode($signature);

        //设置调试数据
        $this->setDebugData($param,$signature,$pubKey);

        // 验证签名
        return (bool)openssl_verify($response, $signature, $pubKeyId, OPENSSL_ALGO_SHA256);
    }

    /**
     * 使用私钥对接口提交的数据进行签名
     * @param array $param
     * @param $priKey
     * @return string
     */
    public function rsaPriSign(array $param, $priKey): string
    {
        $param      = array_filter($param);
        ksort($param);

        $pem            = str_contains($priKey,'BEGIN PRIVATE KEY') ? $priKey : "-----BEGIN PRIVATE KEY-----\n" .$priKey."\n-----END PRIVATE KEY-----";

        // 获取资源标识符
        $pKeyId         = openssl_pkey_get_private($pem);

        // 私钥加签并赋值给$signature
        openssl_sign(http_build_query($param), $signature, $pKeyId, OPENSSL_ALGO_SHA256);

        //设置调试数据
        $this->setDebugData($param,$signature,$priKey);

        return  base64_encode($signature);
    }

    /**
     * 设置调试数据
     * @param array $param
     * @param string $signature
     * @param string $key
     * @return void
     */
    private function setDebugData(array $param, string $signature,string $key): void
    {
        if ($this->config['debug'] ?? false){
            //用来数据调试
            $this->signData     = [
                'paramJson'     => json_encode($param),
                'paramSort'     => http_build_query($param),
                'signature'     => base64_encode($signature),
                'key'           => $key,
            ];
        }
    }

    /**
     * 获取调试数据，调试用，需要开启 debug 模式
     * @return array
     */
    public function getDebugData(): array
    {
        return $this->signData;
    }

    /**
     * 格式化密钥
     * @param string $key 密钥
     * @return string
     */
    private function formatKey(string $key = ''): string
    {
        return !empty($key) ? str_replace(['-','',' ','BEGIN','PRIVATE','PUBLIC','KEY'],'',$key) : $key;
    }
}